// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/downloader/JmDownloader.java
package dev.jukomu.jmcomic.downloader;

import dev.jukomu.common.base.concurrent.MultiTaskExecutor;
import dev.jukomu.common.util.file.FileUtils;
import dev.jukomu.jmcomic.client.JmcomicClient;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.entity.JmAlbumDetail;
import dev.jukomu.jmcomic.entity.JmImageDetail;
import dev.jukomu.jmcomic.entity.JmPhotoDetail;
import dev.jukomu.jmcomic.exception.ExceptionUtils;
import dev.jukomu.jmcomic.exception.PartialDownloadFailedException;
import dev.jukomu.jmcomic.option.JmOption;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 对应Python的JmDownloader，是下载器的核心实现。
 * 它整合了JmOption和JmcomicClient，并调度下载逻辑。
 */
public class JmDownloader implements DownloadCallback, AutoCloseable {

    protected final JmOption option; // 下载选项
    protected final JmcomicClient client; // 禁漫客户端

    // 成功下载的记录，Key: 本子详情, Value: (Key: 章节详情, Value: (Key: 保存路径, Value: 图片详情))
    protected final Map<JmAlbumDetail, Map<JmPhotoDetail, List<Map.Entry<String, JmImageDetail>>>> downloadSuccessMap;
    // 下载失败的图片记录
    protected final List<Map.Entry<JmImageDetail, Exception>> downloadFailedImage;
    // 下载失败的章节记录
    protected final List<Map.Entry<JmPhotoDetail, Exception>> downloadFailedPhoto;

    /**
     * 构造函数。
     *
     * @param option 下载选项。
     */
    public JmDownloader(JmOption option) {
        this.option = option;
        this.client = option.buildJmClient(); // 创建或获取缓存的客户端
        this.downloadSuccessMap = new ConcurrentHashMap<>();
        this.downloadFailedImage = Collections.synchronizedList(new ArrayList<>());
        this.downloadFailedPhoto = Collections.synchronizedList(new ArrayList<>());
    }

    /**
     * 根据本子ID下载整个本子。
     *
     * @param albumId 本子ID。
     * @return 本子详情对象。
     */
    public JmAlbumDetail downloadAlbum(String albumId) {
        JmAlbumDetail album = client.getAlbumDetail(albumId);
        downloadByAlbumDetail(album);
        return album;
    }

    /**
     * 根据本子详情对象下载整个本子。
     *
     * @param album 本子详情对象。
     */
    public void downloadByAlbumDetail(JmAlbumDetail album) {
        beforeAlbum(album);
        if (album.isSkip()) {
            return;
        }
        executeOnCondition(
                album.toList(), // 使用toList()将IndexedEntity转换为List
                this::downloadByPhotoDetail,
                option.decidePhotoBatchCount(album)
        );
        afterAlbum(album);
    }

    /**
     * 根据章节ID下载整个章节。
     *
     * @param photoId 章节ID。
     * @return 章节详情对象。
     */
    public JmPhotoDetail downloadPhoto(String photoId) {
        JmPhotoDetail photo = client.getPhotoDetail(photoId, true, true);
        downloadByPhotoDetail(photo);
        return photo;
    }

    /**
     * 根据章节详情对象下载整个章节。
     *
     * @param photo 章节详情对象。
     */
    public void downloadByPhotoDetail(JmPhotoDetail photo) {
        try {
            client.checkPhoto(photo);
            beforePhoto(photo);
            if (photo.isSkip()) {
                return;
            }
            executeOnCondition(
                    photo.toList(),
                    this::downloadByImageDetail,
                    option.decideImageBatchCount(photo)
            );
            afterPhoto(photo);
        } catch (Exception e) {
            JmModuleConfig.jmLog("photo.failed", "章节下载失败: [" + photo.getId() + "], 异常: [" + e.getMessage() + "]");
            downloadFailedPhoto.add(new SimpleEntry<>(photo, e));
            // Java中没有直接的装饰器，catch_exception的逻辑在这里实现
            // rethrow the exception to stop further processing if needed
            // throw new RuntimeException("Failed to download photo " + photo.getId(), e);
        }
    }

    /**
     * 根据图片详情对象下载单张图片。
     *
     * @param image 图片详情对象。
     */
    public void downloadByImageDetail(JmImageDetail image) {
        try {
            String imgSavePath = option.decideImageFilepath(image);
            image.setSavePath(imgSavePath);
            image.setExists(FileUtils.fileExists(imgSavePath));

            beforeImage(image, imgSavePath);

            if (image.isSkip()) {
                return;
            }

            boolean useCache = option.decideDownloadCache(image);
            boolean decodeImage = option.decideDownloadImageDecode(image);

            if (useCache && image.isExists()) {
                // 如果使用缓存且文件已存在，则跳过下载
                afterImage(image, imgSavePath); // 仍然调用afterImage以记录成功
                return;
            }

            client.downloadByImageDetail(image, imgSavePath, decodeImage);
            afterImage(image, imgSavePath);
        } catch (Exception e) {
            String errorMessage = "图片下载失败: [" + image.getDownloadUrl() + "], 异常: " + e.toString();
            JmModuleConfig.jmLog("image.failed", errorMessage);
            downloadFailedImage.add(new SimpleEntry<>(image, e));
        }
    }

    /**
     * 通用的并发任务执行器。
     *
     * @param iterObjs   要处理的对象列表。
     * @param apply      对每个对象执行的函数。
     * @param batchCount 并发数。
     * @param <T>        对象类型。
     */
    protected <T> void executeOnCondition(List<T> iterObjs, java.util.function.Consumer<T> apply, int batchCount) {
        List<T> filteredObjs = doFilter(iterObjs);
        if (filteredObjs.isEmpty()) {
            return;
        }

        try {
            MultiTaskExecutor.threadPoolExecutor(
                    filteredObjs,
                    apply,
                    true, // waitFinish
                    batchCount, // maxWorkers
                    null // stopFlag
            );
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            JmModuleConfig.jmLog("downloader.error", "下载任务被中断: " + e.getMessage());
        }
    }

    /**
     * 过滤要下载的对象列表。子类可以覆盖此方法以实现自定义过滤逻辑。
     *
     * @param details 原始对象列表。
     * @param <T>     对象类型。
     * @return 过滤后的对象列表。
     */
    protected <T> List<T> doFilter(List<T> details) {
        return details; // 默认不过滤
    }

    /**
     * 检查是否所有预期的图片都已成功下载。
     *
     * @return 如果全部成功返回 true。
     */
    public boolean isAllSuccess() {
        if (hasDownloadFailures()) {
            return false;
        }
        for (Map.Entry<JmAlbumDetail, Map<JmPhotoDetail, List<Map.Entry<String, JmImageDetail>>>> albumEntry : downloadSuccessMap.entrySet()) {
            JmAlbumDetail album = albumEntry.getKey();
            Map<JmPhotoDetail, List<Map.Entry<String, JmImageDetail>>> photoMap = albumEntry.getValue();
            if (album.size() != photoMap.size()) {
                return false;
            }
            for (Map.Entry<JmPhotoDetail, List<Map.Entry<String, JmImageDetail>>> photoEntry : photoMap.entrySet()) {
                JmPhotoDetail photo = photoEntry.getKey();
                List<Map.Entry<String, JmImageDetail>> imageList = photoEntry.getValue();
                if (photo.size() != imageList.size()) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 检查是否存在下载失败的记录。
     *
     * @return 如果有失败记录返回 true。
     */
    public boolean hasDownloadFailures() {
        return !downloadFailedImage.isEmpty() || !downloadFailedPhoto.isEmpty();
    }

    // --- 回调方法实现 ---
    @Override
    public void beforeAlbum(JmAlbumDetail album) {
        DownloadCallback.super.beforeAlbum(album);
        downloadSuccessMap.computeIfAbsent(album, k -> new ConcurrentHashMap<>());
        // option.callAllPlugin("before_album", true, new HashMap<String, Object>() {{ put("album", album); put("downloader", JmDownloader.this); }});
    }

    @Override
    public void afterAlbum(JmAlbumDetail album) {
        DownloadCallback.super.afterAlbum(album);
        // option.callAllPlugin("after_album", true, new HashMap<String, Object>() {{ put("album", album); put("downloader", JmDownloader.this); }});
    }

    @Override
    public void beforePhoto(JmPhotoDetail photo) {
        DownloadCallback.super.beforePhoto(photo);
        downloadSuccessMap.computeIfAbsent(photo.getFromAlbum(), k -> new ConcurrentHashMap<>())
                .computeIfAbsent(photo, k -> Collections.synchronizedList(new ArrayList<>()));
        // option.callAllPlugin("before_photo", true, new HashMap<String, Object>() {{ put("photo", photo); put("downloader", JmDownloader.this); }});
    }

    @Override
    public void afterPhoto(JmPhotoDetail photo) {
        DownloadCallback.super.afterPhoto(photo);
        // option.callAllPlugin("after_photo", true, new HashMap<String, Object>() {{ put("photo", photo); put("downloader", JmDownloader.this); }});
    }

    @Override
    public void beforeImage(JmImageDetail image, String imgSavePath) {
        DownloadCallback.super.beforeImage(image, imgSavePath);
        // option.callAllPlugin("before_image", true, new HashMap<String, Object>() {{ put("image", image); put("downloader", JmDownloader.this); }});
    }

    @Override
    public void afterImage(JmImageDetail image, String imgSavePath) {
        DownloadCallback.super.afterImage(image, imgSavePath);
        JmPhotoDetail photo = image.getFromPhoto();
        JmAlbumDetail album = photo.getFromAlbum();
        downloadSuccessMap.get(album).get(photo).add(new SimpleEntry<>(imgSavePath, image));
        // option.callAllPlugin("after_image", true, new HashMap<String, Object>() {{ put("image", image); put("downloader", JmDownloader.this); }});
    }

    /**
     * 如果存在下载失败的记录，则抛出PartialDownloadFailedException。
     *
     * @throws PartialDownloadFailedException 如果存在下载失败。
     */
    public void raiseIfHasException() {
        if (!hasDownloadFailures()) {
            return;
        }
        StringBuilder msg = new StringBuilder("部分下载失败\n\n");
        if (!downloadFailedPhoto.isEmpty()) {
            msg.append(String.format("共%d个章节下载失败: %s\n", downloadFailedPhoto.size(), downloadFailedPhoto));
        }
        if (!downloadFailedImage.isEmpty()) {
            msg.append(String.format("共%d个图片下载失败: %s\n", downloadFailedImage.size(), downloadFailedImage));
        }
        ExceptionUtils.raises(
                msg.toString(),
                Collections.singletonMap("downloader", this),
                PartialDownloadFailedException.class
        );
    }

    /**
     * 对应Python的with语法，实现AutoCloseable接口。
     * 在try-with-resources块结束时调用。
     */
    @Override
    public void close() {
        // 在Python中，__exit__用于处理异常。
        // 在Java中，try-with-resources的close方法通常用于资源清理。
        // 这里的JmDownloader没有需要显式关闭的资源，但可以用于打印最终日志或检查异常。
        JmModuleConfig.jmLog("downloader.exit", "下载器 " + getClass().getSimpleName() + " 任务结束。");
    }

    /**
     * 用于在JmModuleConfig中注册此下载器类。
     */
    public static void useAsDefault() {
        JmModuleConfig.CLASS_DOWNLOADER = JmDownloader.class;
    }
}